package com.mostka.rpc.server;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;

import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;

import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import com.google.gson.Gson;
import com.mostka.rpc.client.WsRpcServer;
import com.mostka.rpc.util.InstanceCreator;

@SuppressWarnings("serial")
public class WsRpcServerServlet extends WebSocketServlet implements TextMessageHandler {
	
	class ServerImplementation{
		private String clientClassName;
		private HasMethodArgs serverargsInstance;
		public ServerImplementation(String  clientClassName, HasMethodArgs serverargsInstance) {
			this.clientClassName = clientClassName;
			this.serverargsInstance = serverargsInstance;
		}
		public String getClientClass() {
			return clientClassName;
		}
		public HasMethodArgs getServerArgsInstance() {
			return serverargsInstance;
		}
		
	}
	
	HashMap<Integer, Object> clients = new HashMap<>();
	private HashMap<String, ServerImplementation> implementations = new HashMap<>();
	
	@SuppressWarnings("rawtypes")
	public WsRpcServerServlet() {
		try {
			Class<?>[] interfaces = this.getClass().getInterfaces();
			for (Class<?> interfac : interfaces) {
				Type[] types = interfac.getGenericInterfaces();
				for (Type type :types){
					if (type.getClass().equals(ParameterizedTypeImpl.class) && ((Class) ((ParameterizedType) type).getRawType()).equals(WsRpcServer.class)){
						Type[] args = ((ParameterizedType) type).getActualTypeArguments();
						if (args.length==1){
							String serverInterfacName = interfac.getCanonicalName();
							Class clientClass = (Class) args[0];
							String  clientClassName = clientClass.getCanonicalName()+"__ClientServerWsRpcGenerated";
							HasMethodArgs argsInstance = (HasMethodArgs) InstanceCreator.getObjectInstance(serverInterfacName+"__AsyncArgsWsRpcGenerated");
							implementations.put(argsInstance.getHashName(), new ServerImplementation(clientClassName, argsInstance));
						}
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
		String[] s = request.getPathInfo().split("/");
		String channel = s[1];
		String hashClassName = s[2];
		return new Socket(this, channel, hashClassName);
	}

	@Override
	public void onMessage(int token, String channel, String message) {
		Object client = clients.get(token);
		if (client==null){
			System.out.println("clints with token not found "+token);
			return;
		}
		String[] messageParts = message.split("_---_");
		if (!implementations.containsKey(messageParts[0])){
			System.out.println("implementation class by hash name '"+messageParts[0]+"' not found!");
			return;
		}
		int methodPos;
		try{
			methodPos = Integer.parseInt(messageParts[1]);
		}catch(NumberFormatException  e){
			System.out.println("bad method pos format!");
			return;
		}
		ServerImplementation impl = implementations.get(messageParts[0]);
		Class<?>[] paramClasses = impl.getServerArgsInstance().getMethodArgs(methodPos);
		String methodName = impl.getServerArgsInstance().getMethodName(methodPos);
		if (paramClasses==null || methodName==null || messageParts.length-2!=paramClasses.length-2){
			System.out.println("bad message format");
			return;
		}
		Object[] decodedParams = new Object[paramClasses.length]; 
		decodedParams[0]=channel;
		decodedParams[1]=client;
		Gson gson = new Gson();
		
		for(int i=2;i<paramClasses.length;i++){
			decodedParams[i] = gson.fromJson(messageParts[i], paramClasses[i]);
		}
		Method method;
		try {
			method = getClass().getMethod(methodName, paramClasses);
		} catch (NoSuchMethodException | SecurityException e) {
			System.out.println("Problem with geting method '"+methodName+"' of actual setvlet");
			e.printStackTrace();
			return;
		}
		try {
			method.invoke(this, decodedParams);
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			System.out.println("Problem with ivoking method '"+methodName+"' of actual setvlet");
			System.out.println(gson.toJson(decodedParams));
			e.printStackTrace();
			return;
		}
	}

    protected void disconnect(int token, int code, String message){
        Socket socket = sockets.get(token);
        if (socket!=null){
            socket.close(code,message);
        }
    }
    private HashMap<Integer, Socket> sockets = new HashMap<>();
	@SuppressWarnings("unchecked")
	@Override
	public void onJoin(Socket socket, int token, String channel, String hashClassName) {
		ServerImplementation impl = implementations.get(hashClassName);
		if (impl==null){
			System.out.println("clints with token not found "+token);
			socket.close(Socket.CLOSE_BAD_HASH, "bad hash");
			return;
		}
		try {
			Object clientServer = InstanceCreator.getObjectInstance(impl.getClientClass(), socket, token);
			clients.put(token, clientServer);
            sockets.put(token,socket);
			@SuppressWarnings("rawtypes")
			WsRpcServer s = (WsRpcServer) this;
			s.onJoin(channel, clientServer);
		} catch (Exception e) {
			socket.close(Socket.CLOSE_SERVER_CRASH, "unable to create client class");
			e.printStackTrace();
		}
	}

	@Override
	public void onClose(int token, String channel) {
		@SuppressWarnings("rawtypes")
		WsRpcServer s = (WsRpcServer) this;
		s.onClose(channel, token);
        clients.remove(token);
        sockets.remove(token);
	}
}
